﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using Nova.Parsing;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents a named <see cref="Block"/> of code with optional parameters and an optional return value.
    /// Various optional modifiers can also be used (the default is 'private').
    /// </summary>
    public class MethodDecl : MethodDeclBase
    {
        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="MethodDecl"/> with the specified name, return type, and modifiers.
        /// </summary>
        public MethodDecl(string name, Expression returnType, Modifiers modifiers, CodeObject body, params ParameterDecl[] parameters)
            : base(name, returnType, modifiers, body, parameters)
        { }

        /// <summary>
        /// Create a <see cref="MethodDecl"/> with the specified name, return type, and modifiers.
        /// </summary>
        public MethodDecl(string name, Expression returnType, Modifiers modifiers, params ParameterDecl[] parameters)
            : base(name, returnType, modifiers, parameters)
        { }

        /// <summary>
        /// Create a <see cref="MethodDecl"/> with the specified name and return type.
        /// </summary>
        public MethodDecl(string name, Expression returnType, CodeObject body, params ParameterDecl[] parameters)
            : base(name, returnType, body, parameters)
        { }

        /// <summary>
        /// Create a <see cref="MethodDecl"/> with the specified name and return type.
        /// </summary>
        public MethodDecl(string name, Expression returnType, params ParameterDecl[] parameters)
            : base(name, returnType, parameters)
        { }

        /// <summary>
        /// Create a <see cref="MethodDecl"/> with the specified name and return type.
        /// </summary>
        public MethodDecl(Expression name, Expression returnType, CodeObject body, params ParameterDecl[] parameters)
            : base(name, returnType, body, parameters)
        { }

        /// <summary>
        /// Create a <see cref="MethodDecl"/> with the specified name and return type.
        /// </summary>
        public MethodDecl(Expression name, Expression returnType, params ParameterDecl[] parameters)
            : base(name, returnType, new Block(), parameters)
        { }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Create a reference to the <see cref="MethodDecl"/>.
        /// </summary>
        /// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
        /// <returns>A <see cref="MethodRef"/>.</returns>
        public override SymbolicRef CreateRef(bool isFirstOnLine)
        {
            return new MethodRef(this, isFirstOnLine);
        }

        #endregion

        #region /* PARSING */

        internal static void AddParsePoints()
        {
            // Methods are only valid with a TypeDecl parent, but we'll allow any IBlock so that we can
            // properly parse them if they accidentally end up at the wrong level (only to flag them as errors).
            // This also allows for them to be embedded in a DocCode object.
            // Use a parse-priority of 50 (ConstructorDecl uses 0, LambdaExpression uses 100, Call uses 200, Cast uses 300, Expression parens uses 400).
            Parser.AddParsePoint(ParseTokenStart, 50, Parse, typeof(IBlock));
        }

        /// <summary>
        /// Parse a <see cref="MethodDecl"/>.
        /// </summary>
        public static MethodDeclBase Parse(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // If our parent is a TypeDecl, verify that we have an unused Expression (it can be either an
            // identifier or a Dot operator for explicit interface implementations).  Otherwise, require a
            // possible return type in addition to the Expression.
            // If it doesn't seem to match the proper pattern, abort so that other types can try parsing it.
            if ((parent is TypeDecl && parser.HasUnusedExpression) || parser.HasUnusedTypeRefAndExpression)
            {
                // If we have a Dot expression with an UnresolvedRef with type arguments on the right side,
                // then this is a special-case GenericMethodDecl and we need to turn parsing over to that
                // class (it's normal parsing of the type arguments fails to activate due to the Dot operator
                // activating expression parsing).
                Dot dot = parser.LastUnusedCodeObject as Dot;
                if (dot != null && dot.Right is UnresolvedRef && ((UnresolvedRef)dot.Right).HasTypeArguments)
                    return new GenericMethodDecl(parser, parent, true, flags);

                return new MethodDecl(parser, parent, true, flags);
            }
            return null;
        }

        protected MethodDecl(Parser parser, CodeObject parent, bool parse, ParseFlags flags)
            : base(parser, parent)
        {
            if (parse)
            {
                ParseMethodNameAndType(parser, parent, true, false);
                ParseParameters(parser);
                ParseModifiersAndAnnotations(parser);  // Parse any attributes and/or modifiers
                ParseTerminatorOrBody(parser, flags);
            }
        }

        #endregion
    }
}
